<PageCard>

# useListNavigation

Adds arrow key-based navigation of a list of items, either using
real DOM focus or virtual focus.

```js
import {useListNavigation} from '@floating-ui/react';
```

This is useful for creating floating elements that contain a list
of items (such as a menu, select, or combobox) to ensure that
keyboard usage can navigate the list.

<ShowFor packages={['react-dom']}>

<PackageLimited>@floating-ui/react only</PackageLimited>

</ShowFor>

</PageCard>

- See [`FloatingList`](/docs/FloatingList) for creating
  composable children API components.
- See [`Composite`](/docs/Composite) for list navigation outside
  of floating element contexts.

## Usage

This Hook returns event handler props and ARIA attribute props.

To use it, pass it the `context{:.const}` object returned from
`useFloating(){:js}` or
[`useFloatingRootContext`](/docs/useInteractions#external-reference),
and then feed its result into the `useInteractions(){:js}` array.
The returned prop getters are then spread onto the elements for
rendering. `getItemProps(){:js}` is spread to each list item.

The `listRef{:.const}` holds an array of HTML elements. The
`activeIndex{:.const}` determines which index of the list is
currently active (focused or highlighted).

When using real DOM focus (default), the list items **must be
focusable** and should have an appropriate `role{:.keyword}` prop
based on the role of the floating element.

```js /context/
function App() {
  const [activeIndex, setActiveIndex] = useState(null);

  const {refs, floatingStyles, context} = useFloating({
    open: true,
  });

  const listRef = useRef([]);

  const listNavigation = useListNavigation(context, {
    listRef,
    activeIndex,
    onNavigate: setActiveIndex,
  });

  const {getReferenceProps, getFloatingProps, getItemProps} =
    useInteractions([listNavigation]);

  const items = ['one', 'two', 'three'];

  return (
    <>
      <div ref={refs.setReference} {...getReferenceProps()}>
        Reference element
      </div>
      <div
        ref={refs.setFloating}
        style={floatingStyles}
        {...getFloatingProps()}
      >
        {items.map((item, index) => (
          <div
            key={item}
            // Make these elements focusable using a roving tabIndex.
            tabIndex={activeIndex === index ? 0 : -1}
            ref={(node) => {
              listRef.current[index] = node;
            }}
            {...getItemProps()}
          >
            {item}
          </div>
        ))}
      </div>
    </>
  );
}
```

## Examples

- [Select](https://codesandbox.io/s/gallant-sea-rcg43b?file=/src/Select.tsx)
- [Combobox](https://codesandbox.io/s/fragrant-water-bsuirj?file=/src/App.tsx)

## Using with `FloatingFocusManager{:.class}`

`useListNavigation(){:js}` and `<FloatingFocusManager>{:js}` both
manage focus but in different ways. To ensure they work together
properly, the initial point of focus needs to be considered.

The focus manager by default focuses the first _tabbable_ (not
focusable) element inside of the floating element. If none are,
it falls back to the floating element itself. This allows keydown
events to work for pointer input (e.g. open with mouse, then
start navigating with arrow keys).

1. For a **combobox**, where the input should keep focus, set it
   to `-1{:js}` so focus doesn't move at all:

```js /initialFocus={-1}/
<FloatingFocusManager context={context} initialFocus={-1}>
  {/* floating element */}
</FloatingFocusManager>
```

2. For other types of components, like a **menu** or a
   **select**, where you want focus to move inside the floating
   element, the default value works, but make sure your list
   items aren't tabbable if the `activeIndex` is `null{:js}`.

```js
// Not focusable, not tabbable.
<div />
// Tabbable and focusable.
<div tabIndex={0} />
// Not tabbable, but focusable.
<div tabIndex={-1} />
```

A roving tabIndex is the recommended strategy:

```js
<div tabIndex={activeIndex === index ? 0 : -1} />
```

## Props

```ts
interface UseListNavigationProps {
  listRef: React.MutableRefObject<Array<HTMLElement | null>>;
  activeIndex: number | null;
  onNavigate?(index: number | null): void;
  enabled?: boolean;
  selectedIndex?: number | null;
  loop?: boolean;
  nested?: boolean;
  rtl?: boolean;
  virtual?: boolean;
  virtualItemRef?: React.MutableRefObject<HTMLElement | null>;
  allowEscape?: boolean;
  orientation?: 'vertical' | 'horizontal' | 'both';
  cols?: number;
  focusItemOnOpen?: 'auto' | boolean;
  focusItemOnHover?: boolean;
  openOnArrowKeyDown?: boolean;
  disabledIndices?: Array<number>;
  scrollItemIntoView?: boolean | ScrollIntoViewOptions;
  itemSizes?: Dimensions[];
  dense?: boolean;
}
```

### `listRef{:.key}`

<Required />

default: empty list

A ref that holds an array of list items. You can assign each item
in the array by its index like so:

```js
const options = ['one', 'two', 'three'];
const listRef = useRef([]);

return options.map((option, index) => (
  <li
    key={option}
    ref={(node) => {
      listRef.current[index] = node;
    }}
  >
    {option}
  </li>
));
```

### `activeIndex{:.key}`

<Required />

default: `null{:js}`

The currently active (i.e. highlighted or focused) item index,
which may or may not be selected.

```js
const [activeIndex, setActiveIndex] = useState(null);

useListNavigation(context, {
  activeIndex,
});
```

### `onNavigate{:.function}`

default: no-op

Callback invoked when the user navigates, passed in the current
`activeIndex{:.param}`.

```js
const [activeIndex, setActiveIndex] = useState(null);

useListNavigation(context, {
  onNavigate: setActiveIndex,
});
```

### `enabled{:.key}`

default: `true{:js}`

Conditionally enable/disable the Hook.

```js
useListNavigation(context, {
  enabled: false,
});
```

### `selectedIndex{:.key}`

default: `null{:js}`

The currently selected item index, which may or may not be
active.

This is the item shown in the trigger button/input.

```js
const [selectedIndex, setSelectedIndex] = useState(null);

useListNavigation(context, {
  selectedIndex,
});
```

### `loop{:.key}`

default: `false{:js}`

Determines whether focus should loop around when navigating past
the first or last item.

```js
useListNavigation(context, {
  loop: true,
});
```

### `nested{:.key}`

default: `false{:js}`

If the list is nested within another one (e.g. a nested submenu),
the navigation semantics change.

```js
useListNavigation(context, {
  nested: true,
});
```

### `rtl{:.key}`

default: `false{:js}`

Whether the direction of the floating element's navigation is in
RTL layout.

```js
useListNavigation(context, {
  rtl: true,
});
```

### `virtual{:.key}`

default: `false{:js}`

Whether the focus is virtual (using
`aria-activedescendant{:.keyword}`).

Use this if you need focus to remain on the reference element
(such as an input), but allow arrow keys to navigate list items.
This is common in autocomplete listbox components.

```js
useListNavigation(context, {
  virtual: true,
});
```

<Notice type="error" title="Required">
  Your virtually-focused list items must have a unique `id{:.keyword}` set on
  them. If you're using a component role with the `useRole(){:js}` Hook,
  then an `id{:.keyword}` is generated automatically.
</Notice>

### `virtualItemRef{:.key}`

default: `undefined{:js}`

When using virtual focus management, this holds a ref to the
virtually-focused item.

This allows nested virtual navigation to be enabled, and lets you
know when a nested element is virtually focused from the root
reference handling the events.

```js
const virtualItemRef = useRef(null);
useListNavigation(context, {
  virtualItemRef,
});
```

<Notice type="error" title="Required">
  The React tree must be using a
  [`FloatingTree`](/docs/FloatingTree) setup so that
  communication of nested floating elements can occur.
</Notice>

### `allowEscape{:.key}`

Determines whether focus can escape the list, such that nothing
is selected after navigating beyond the boundary of the list. In
some autocomplete/combobox components, this may be desired, as
screen readers will return to the input.

```js
useListNavigation(context, {
  loop: true,
  allowEscape: true,
});
```

<Notice type="error" title="Required">
  The `loop{:.key}` option must be set to `true{:js}`.
</Notice>

### `orientation{:.key}`

default: `'vertical'{:js}`

The orientation in which navigation occurs.

```js
useListNavigation(context, {
  orientation: 'horizontal',
});
```

### `cols{:.key}`

default: `1{:js}`

Specifies how many columns the list has (i.e., it's a grid).

Use an orientation of `'horizontal'{:js}` (e.g. for an emoji
picker/date picker, where pressing ArrowRight or ArrowLeft can
change rows), or `'both'{:js}` (where the current row cannot be
escaped with ArrowRight or ArrowLeft, only ArrowUp and
ArrowDown).

```js
useListNavigation(context, {
  // 4 columns, any number of rows
  cols: 4,
});
```

### `focusItemOnOpen{:.key}`

default: `'auto'{:js}`

Whether to focus the item upon opening the floating element.
`'auto'{:js}` infers what to do based on the input type (keyboard
vs. pointer), while a boolean value will force the value.

```js
useListNavigation(context, {
  focusItemOnOpen: true,
});
```

### `focusItemOnHover{:.key}`

default: `true{:js}`

Whether hovering an item synchronizes the focus.

```js
useListNavigation(context, {
  focusItemOnHover: false,
});
```

### `openOnArrowKeyDown{:.key}`

default: `true{:js}`

Whether pressing an arrow key on the navigation's main axis opens
the floating element.

```js
useListNavigation(context, {
  openOnArrowKeyDown: false,
});
```

### `disabledIndices{:.key}`

default: `undefined{:js}`

By default elements with either a `disabled{:.keyword}` or
`aria-disabled{:.keyword}` attribute are skipped in the list
navigation -- however, this requires the items to be rendered.

This prop allows you to manually specify indices which should be
disabled, overriding the default logic.

For Windows-style select menus, where the menu does not open when
navigating via arrow keys, specify an empty array.

```js
useListNavigation(context, {
  disabledIndices: [0, 3],
});
```

### `scrollItemIntoView{:.key}`

default: `true{:js}` | `ScrollIntoViewOptions{:.class}`

Whether to scroll the active item into view when navigating. The
default value uses `nearest{:.string}` options.

```js
useListNavigation(context, {
  scrollItemIntoView: false,
});
```

### `itemSizes{:.key}`

default: `undefined{:js}`

Only for grid navigation, an array of `Dimensions` objects, which
specify the width (number of columns) and height (number of rows)
of each item, so the navigation algorithm can take the variable
sizes into account. If not specified, every item will be treated
as if it has a size of 1 row and 1 column.

```js
useListNavigation(context, {
  itemSizes: [
    {width: 2, height: 2},
    {width: 1, height: 3},
  ],
});
```

### `dense{:.key}`

default: `false{:js}`

Only for grid navigation, determines if the grid positioning
algorithm should follow CSS Grid's `auto-flow` `dense` algorithm.

```js
useListNavigation(context, {
  dense: true,
});
```
